#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/synch.h"
#include "threads/thread.h"
#include "threads/vaddr.h"
#include "devices/shutdown.h"
#include "userprog/pagedir.h"
#include "userprog/process.h"
#include "filesys/off_t.h"
#include "filesys/filesys.h"
#include "filesys/file.h"
#include "devices/input.h"

static void syscall_handler (struct intr_frame *);


/* syscall handler function  */
static void sys_exit(struct intr_frame *);
static void sys_exec(struct intr_frame *);
static void sys_wait(struct intr_frame *);
static void sys_create(struct intr_frame *);
static void sys_remove(struct intr_frame *);
static void sys_open(struct intr_frame*);
static void sys_filesize(struct intr_frame*);
static void sys_read(struct intr_frame*);
static void sys_write(struct intr_frame*);
static void sys_seek(struct intr_frame*);
static void sys_tell(struct intr_frame*);
static void sys_close(struct intr_frame*);

/* helper function */
static bool check_fd(int fd);
static bool check_ptr(void * ptr);
static bool check_buf(void * buf, int size);


void
syscall_init (void)
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
  lock_init(&file_lock);
}

static void
syscall_handler (struct intr_frame *f UNUSED)
{
  void * esp = f->esp;
  if(!check_ptr(esp) || !check_ptr(esp+12)){
    terminate_process(-1);
  }
  int syscall_num = *(int*)esp;
  switch(syscall_num){
  case SYS_HALT:
    shutdown_power_off();
    break;
  case SYS_EXIT:
    sys_exit(f);
    break;
  case SYS_EXEC:
    sys_exec(f);
    break;
  case SYS_WAIT:
    sys_wait(f);
    break;
  case SYS_CREATE:
    sys_create(f);
    break;
  case SYS_REMOVE:
    sys_remove(f);
    break;
  case SYS_OPEN:
    sys_open(f);
    break;
  case SYS_FILESIZE:
    sys_filesize(f);
    break;
  case SYS_READ:
    sys_read(f);
    break;
  case SYS_WRITE:
    sys_write(f);
    break;
  case SYS_SEEK:
    sys_seek(f);
    break;
  case SYS_TELL:
    sys_tell(f);
    break;
  case SYS_CLOSE:
    sys_close(f);
    break;
  default:
    printf("Invalid System Call Number\n");
    break;
  }
}

static void sys_create(struct intr_frame *f){
  char * name = *(char**)(f->esp+4);
  off_t size = *(off_t*)(f->esp+8);
  bool success = false;
  //check name ptr (size is handled by filesys_create)
  if(!check_ptr(name)){
    terminate_process(-1);
  }
  //acquire lock for filesys operation
  lock_acquire(&file_lock);
  success = filesys_create(name,size);
  lock_release(&file_lock);
  f->eax = success;
  return;
}

static void sys_remove(struct intr_frame *f){
  char * name = *(char**)(f->esp+4);
  bool success = false;
  //check name ptr
  if(!check_ptr(name)){
    terminate_process(-1);
  }
  lock_acquire(&file_lock);
  success = filesys_remove(name);
  lock_release(&file_lock);
  f->eax = success;
  return;
}

static void sys_open(struct intr_frame *f){
  struct thread * cur = thread_current();
  char * name = *(char**)(f->esp+4);
  int fd= cur->last_fd; // new fd allocated from last_fd	
  struct file * file;
  /* check if fd is on the table  and check name ptr*/
  if(!check_ptr(name)){
    terminate_process(-1);
  }
  lock_acquire(&file_lock);
  file = filesys_open(name);
  lock_release(&file_lock);
  if( file != NULL){
    f->eax = fd; // return fd
    cur->fd_table[fd] = file;
    cur->last_fd++; //update last_fd for future file descriptors
  }
  else
    f->eax = -1;
  return;
}

static void sys_filesize(struct intr_frame * f){
  struct thread * cur = thread_current();
  struct file ** fd_table = cur->fd_table;
  int fd = *(int*)(f->esp+4);
  if(!check_fd(fd)){
    terminate_process(-1);
  }
  lock_acquire(&file_lock);
  f->eax = file_length(fd_table[fd]);
  lock_release(&file_lock);
  return;
}

static void sys_read(struct intr_frame *f){
  int fd = *(int*)(f->esp+4);
  char * buf = *(char **)(f->esp+8);
  unsigned int size = *(unsigned int *)(f->esp+12);
  unsigned int i;
  off_t nbyte;
  struct thread * cur = thread_current();
  struct file ** fd_table = cur->fd_table;

  /* check args,esp,fd,buf */
  if(!check_fd(fd) || !check_buf(buf,size)){
    terminate_process(-1);
    return;
  }
  
  /* case for console IO */
  if( fd == STDIN_FILENO){
    for(i=0; i<size;i++){
      buf[i] = input_getc();
    }
    f->eax = size;
    return;
  }
  else if( fd == STDOUT_FILENO){
    //print error and terminate process
    terminate_process(-1);
    return;
  }
  else{ //normal open file descriptor
    lock_acquire(&file_lock);
    nbyte = file_read(fd_table[fd],buf,size);
    lock_release(&file_lock);
    f->eax = nbyte;
    return;
  }
}

static void sys_write(struct intr_frame *f){
  int fd = *(int*)(f->esp+4);
  char * buf = *(char **)(f->esp+8);
  unsigned int size = *(unsigned int *)(f->esp+12);

  off_t nbyte;
  struct thread * cur = thread_current();
  struct file ** fd_table = cur->fd_table;

  /* check args,esp,fd,buf */
  if(!check_fd(fd) || !check_buf(buf,size)){
    terminate_process(-1);
    return;
  }
  /* case for console IO */
  if( fd == STDOUT_FILENO){
    //    printf("writing to console\n");
    putbuf(buf,size);
    f->eax = size;
    return;
  }
  else if( fd == STDIN_FILENO){
    terminate_process(-1);
    return;
  }
  else{ //normal open file descriptor
    lock_acquire(&file_lock);
    nbyte = file_write(fd_table[fd],buf,size);
    lock_release(&file_lock);
    f->eax = nbyte;
    return;
  }
}

static void sys_seek(struct intr_frame *f){
  int fd = *(int*)(f->esp+4);
  off_t pos = *(off_t*)(f->esp+8);
  struct thread * cur = thread_current();
  struct file ** fd_table = cur->fd_table;

  if(!check_fd(fd)){
    terminate_process(-1);
    return;
  }
  if(fd == STDIN_FILENO || fd == STDOUT_FILENO){
    terminate_process(-1);
    return;
  }
  else{
    lock_acquire(&file_lock);
    file_seek(fd_table[fd],pos);
    lock_release(&file_lock);
    return ;
  }
}

static void sys_tell(struct intr_frame *f){
  int fd = *(int*)(f->esp+4);
  struct thread * cur = thread_current();
  struct file ** fd_table = cur->fd_table;

  if(check_fd(fd) == false){
    terminate_process(-1);
    return;
  }
  if(fd == STDIN_FILENO || fd == STDOUT_FILENO){
    //fd for console io
    //print error and terminate
    return;
  }
  else{
    lock_acquire(&file_lock);
    f->eax = file_tell(fd_table[fd]);
    lock_release(&file_lock);
    return;
  }
}

static void sys_close(struct intr_frame *f){
  int fd = *(int*)(f->esp+4);
  struct thread * cur = thread_current();
  struct file ** fd_table = cur->fd_table;
  if(check_fd(fd) == false){
    terminate_process(-1);
    return;
    //terminate
  }
  if(fd == STDIN_FILENO || fd == STDOUT_FILENO){
    terminate_process(-1);
    return;
  }
  else{
    lock_acquire(&file_lock);
    file_close(fd_table[fd]);
    fd_table[fd] = NULL;
    lock_release(&file_lock);
    return;
  }
}

static void sys_exit(struct intr_frame *f){
  int status = *(int*)(f->esp+4);
  f->eax = terminate_process(status);
  return;
}

static void sys_exec(struct intr_frame *f){
  char * cmd_line = *(char**)(f->esp+4);
  if(!check_ptr(cmd_line)){
     terminate_process(-1);
     return;
  }
  lock_acquire(&file_lock);
  f->eax = process_execute(cmd_line);
  lock_release(&file_lock);
  return;
}

static void sys_wait(struct intr_frame *f){
  tid_t tid = *(tid_t*)(f->esp+4);
  f->eax = process_wait(tid);
}

/* check fd if the fd is valid for current process return true if
 found , false otherwise*/

static bool check_fd(int fd)
{
  struct thread * cur = thread_current();
  struct file ** fd_table = cur->fd_table;
  int i;
  /* true for fd == 0 and 1 (console IO) */
  if( fd == 0 || fd == 1)
    return true;
  if( fd< 0 || fd >= 1000)
    return false;
  for(i =2; i< 1000; i++){
    if(fd_table[fd] != NULL)
      return true;
  }
  return false;
}

static bool check_ptr(void * ptr){
  struct thread * cur = thread_current();
  if(ptr == NULL || !is_user_vaddr(ptr)|| 
     pagedir_get_page(cur->pagedir,ptr)== NULL)
    return false;
  else
    return true;
}

static bool check_buf(void * buf,int size){
  char * tmp = (char*) buf;
  int i;
  for(i=0 ;i<size;i++)
    if(!check_ptr(tmp+i))
       return false;
  return true;
}

int terminate_process(int status){
  struct thread * cur = thread_current();
  struct child_process * child = get_child_by_tid(cur->tid,cur->parent);
  child->exit_status = status;
  printf ("%s: exit(%d)\n",thread_name(),status);
  thread_exit();
}
